package solutions.ferpa.ztest.intf;

import br.com.telescope.adsrt.dom.DomFlag;
import br.com.telescope.adsrt.dom.DomInteger;
import br.com.telescope.adsrt.dom.DomSourceCode;
import br.com.telescope.adsrt.dom.DomText;
import br.com.telescope.adsrt.lib.*;
import br.com.telescope.t2.ServiceModel;
import br.com.telescope.t2.ServiceUrlManager;
import br.com.telescope.t2.config.Config;
import br.com.telescope.t2.config.ConfigParameter;
import br.com.telescope.t2.exception.ApplicationRuntimeException;
import br.com.telescope.t2.exception.UnexpectedException;
import br.com.telescope.t2.feature.AbstractFeature;
import br.com.telescope.t2.feature.CustomFeature;
import br.com.telescope.t2.feature.MethodFeature;
import br.com.telescope.util.CsvExportSource;
import br.com.telescope.util.HtmlUtil;
import br.com.telescope.util.JdbcUtil;
import br.com.telescope.util.WebFileTemp;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import solutions.ferpa.ztest.ent.*;

/**
 * Executor de SQL e DML.
 * <p>
 * Interface que permite ao usuário executar consultas e DMLs livremente 
 * no banco de dados.
 * <P>
 * O usuário pode escrever vários comandos em sequencia em uma mesma 
 * entrada. Estes comandos serão executados em uma transação e 
 * automaticamente comitados no final.
 * <P>
 * 
 * <P>
 * O resultado dos comandos SELECT executados será através de uma 
 * tabela.
 * <P>
 * 
 * <P>
 * Para os demais comandos de DML ou DDL, apenas o número de registros 
 * processados será informado.
 * <P>
 * 
 * <P>
 * Além dos comandos tradicionais de SQL, DML e DDL, os seguintes 
 * comandos são reconhecidos:
 * <P>
 * * DESC tabela - Apresenta as informações sobre a tabela informada 
 * (colunas e tipagem de dados)
 * <P>
 * * COMMIT - Executa um commit na conexão.
 * <P>
 * * ROLLBACK - Executa um rollback na conexão.
 * <P>
 * * SHOW pref, pref, ... - Mostra os valores dos parâmetros de 
 * configuração. Caso não informar parâmetros, lista todos os 
 * parâmetros.
 * <P>
 * * CONFIG pref=valor, pref=valor, ... - Permite configurar 
 * preferencias
 * <P>
 * * CONNECT [nome] - Conecta com outra base de dados. Caso não 
 * informado, vola a conectar com a base DEFAULT.
 * <P>
 * * SUM col1 col2 ... - Estabelece uma lista de colunas que deseja que 
 * sejam totalizadas no final.
 * <P>
 * * SERVICE nome-global-serviço - Carrega um serviço para executar 
 * funcionalidades dele;
 * <P>
 * * PARMS nome=valor, nome=valor, ... - Define valores para serem 
 * utilizados na execução das funcionalidades de um service a seguir.
 * <P>
 * * RUN [feature [action]] - Executa uma função do service carregado.
 * <P>
 * * SELECT - Permite consultar os dados de um SQL (em forma de tabela).
 * <P>
 * * DISPLAY - Funciona exatamente igual ao SELECT, porém, invés de 
 * apresentar o resultado em uma tabela, apresenta os campos um abaixo do 
 * outro no formato de um formulário.
 * <P>
 * * EXPORT - Permite exportar uma consulta SQL para um CSV.
 * <P>
 * * IF-NOT-FOUND - Executa o comando apenas se o ultimo comando não 
 * encontrou registros.
 * <P>
 * * DML-INSERT - Permite criar um comando de INSERT com os dados de um 
 * determinado registro.
 * <P>
 * * DML-UPDATE - Permite criar um comando de UPDATE com os dados de um 
 * determinado registro.
 * <P>
 * * DML-SAVE - Equivale a um comando DML-UPDATE seguido de um DML-INSERT 
 * precedido de um IF-NOT-FOUND. Serve para gerar um script repassando um 
 * grupo de dados de uma base para a outra (sem importar se precisa 
 * incluir ou alterar).
 * <P>
 * 
 * <P>
 * 
 * <P>
 * Todos os DML e DDL registrados são registrados no log de replicação, 
 * ou seja, são automaticamente replicados para os outros servidores 
 * conectados à base de replicação.
 * <P>
 * 
 * <P>
 * 
 * <P>
 * Comandos especiais:
 * <P>
 * 
 * <P>
 * ==LET==
 * <P>
 * Permite definir uma variável com um valor.
 * <P>
 * Caso a segunda variável ser um record, cria um novo record com os 
 * mesmos atributos.
 * <P>
 * Ex:
 * <P>
 *  let user = last;
 * <P>
 * 
 * <P>
 * 
 * <P>
 * ==SHOW==
 * <P>
 * Este comando permite consultar as propriedades de configuração do 
 * ambiente. Estas propriedades são consultadas diretamente no mapa de 
 * configurações. A sintaxe de uso é:
 * <P>
 *  show [chave] [,chave...];
 * <P>
 * 
 * <P>
 * As chaves são formadas pelo nome global da propriedade. Cada chave 
 * pode iniciar ou terminar com "*" para visualizar grupos de 
 * parâmetros.
 * <P>
 * 
 * <P>
 * Exemplos:
 * <P>
 * Listar uma determinada propriedade específica:
 * <P>
 *  show RT.UI.CUSTOM_CSS;
 * <P>
 * 
 * <P>
 * Listar todas as customizações de CSS:
 * <P>
 *  show *.CSS;
 * <P>
 * 
 * <P>
 * Listar todas as configurações de uma determinada interface:
 * <P>
 *  show PROJET.CAD_OCORRENCIAS.*
 * <P>
 * 
 * <P>
 * Listar as configurações de DB:
 * <P>
 *  show CONFIG.DB.*;
 * <P>
 * 
 * <P>
 * ==DML-INSERT e DML-UPDATE==
 * <P>
 * Estes comandos permitem gerar scripts contendo comandos de INSERT ou 
 * UPDATE com os mesmos dados que estão atualmente na base.
 * <P>
 * A sintaxe é a seguinte:
 * <P>
 *  DML-INSERT|DML-UPDATE TABELA [WHERE ...] [ORDER BY ...] [LIMIT] 
 * [OFFSET]
 * <P>
 * Onde:
 * <P>
 * - TABELA - Nome da tabela a ser utilizada. Esta tabela deverá ter 
 * obrigatoriamente uma coluna nome ID e que deve ser chave primária.
 * <P>
 * - WHERE - Condição de filtro dos registros atuais
 * <P>
 * - ORDERS BY - Ordenação com a qual deseja gerar os comandos
 * <P>
 * - LIMIT, OFFSET - Paginação dos registros desejados.
 * <P>
 * 
 * <P>
 * Exemplos:
 * <P>
 *  dml-insert preferencias where preferencia like 'APP.%';
 * <P>
 *  dml-update atividades where id=2878487;
 * <P>
 * 
 * <P>
 * == Comando IF-NOT-FOUND ==
 * <P>
 * Executa comando apenas se o comando anterior não achou registros
 * <P>
 *  if-not-found comando
 * <P>
 * 
 * <P>
 * Exemplo:
 * <P>
 *  update ... where id=...;
 * <P>
 *  if-not-found insert into ...;
 * <P>
 * 
 * <P>
 * == Comando DML-SAVE ==
 * <P>
 * Combina os comandos DML-UPDATE, DML-INSERT e IF-NOT-FOUND e permite 
 * copiar (alterando ou inserindo) um conjunto de registros de um BD para 
 * outro.
 * <P>
 * 
 * <P>
 * Faz um update seguido de um insert que é executado apenas caso não 
 * encontrar o registro. 
 * <P>
 * 
 * <P>
 * Exemplo: Criar script para atualizar todas as naturezas de operação
 * <P>
 *  dml-save naturezas_operacoes;
 * <P>
 * 
 * <P>
 * Irá gerar várias linhas tipo:
 * <P>
 *  update ... where id=...;
 * <P>
 *  if-not-found insert into ...;
 * <P>
 * 
 * <P>
 * ==Uso de macros==
 * <P>
 * Dentro de qualquer comando informado, pode-se utilizar variáveis 
 * (macros) resultantes de comandos anteriores. Estas variáveis são 
 * definidas no seguinte formato ${nome-da-variavel}.
 * <P>
 * 
 * <P>
 * Qualquer comando SELECT executado, define automaticamente a variável 
 * "last" com os dados do último registro resultante na query. Desta 
 * forma, podemos utilizar o valor de qualquer uma destas colunas através 
 * da macro ${last.coluna}.
 * <P>
 * 
 * <P>
 * Exemplo:
 * <P>
 *  select * from usuarios where login = 'admin';
 * <P>
 *  select * from pedidos where usuario_inclusao_id = ${last.id};
 * <P>
 * 
 * <P>
 * 
 * <P>
 * Telescope origin: RTDBUT.UTL_DB_SQL
 * <P>
 * @author TELESCOPE - Build task T2 Web Interface Generator
 * <P>
 * @version 0.1
 */
public class UtlDbSql extends ServiceModel {
    
    // <editor-fold defaultstate="collapsed" desc="Generated Code">
    public static final String GLOBAL_NAME = "RTDBUT.UTL_DB_SQL";
    
    @Override
    public String globalName() {
        return GLOBAL_NAME;
    }
    
    {
        setTitle("Executor de SQL e DML");
    }
    
    
    // Record items
    private DomSourceCode aCommand;
    private DomInteger aRegistros;
    private DomFlag aReplicar;
    
    // Query items
    
    @Override
    public void initAttributes() {
        
        aCommand = new DomSourceCode();
        aCommand.context(ctx).name("COMMAND").label("Command").height(2).unbound(true);
        
        aRegistros = new DomInteger();
        aRegistros.context(ctx).name("REGISTROS").label("Registros").unbound(true);
        
        aReplicar = new DomFlag();
        aReplicar.context(ctx).name("REPLICAR").label("Replicar").unbound(true);
        
    }
    
    @Override
    public String getDefaultOrderBy() {
        return "";
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="HOME">
    public class FHome extends CustomFeature {
        
        @Override
        public void initForm() {
        }
        
        @Override
        public void preDisplay() {
            try {
                setarFormato();
            } catch (AdsException ex) {
                throw new ApplicationRuntimeException(ex);
            }
        }
        
        @Override
        public void posDisplay() {
            try {
                inicializaReplicacao();
            } catch (AdsException ex) {
                throw new ApplicationRuntimeException(ex);
            }
        }
        
        {
            setFeatureName("HOME");
            setPrimary(false);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="CUSTOM">
    public class FCustom extends CustomFeature {
        
        @Override
        public void initForm() {
            addField(aCommand, "opt");
            addField(aRegistros, "opt");
            addField(aReplicar, "opt");
        }
        
        @Override
        public void preDisplay() {
            try {
                setarFormato();
            } catch (AdsException ex) {
                throw new ApplicationRuntimeException(ex);
            }
        }
        
        @Override
        public void onAction() {
            addSubmit(getFeature("EXECUTAR_COMANDOS")).label("Executar comandos").shortCutKey("F3").icon("salvar.gif");
        }
        
        {
            setFeatureName("CUSTOM");
            setPrimary(false);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="DML">
    public class FDml extends CustomFeature {
        
        @Override
        public void initForm() {
        }
        
        {
            setFeatureName("DML");
            setTitle("DML");
            setPrimary(false);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="EXECUTAR_COMANDOS">
    public class FExecutarComandos extends MethodFeature {
        
        @Override
        public void run() {
            preRun();
            try {
                executarComandos();
            } catch (AdsException ex) {
                throw new ApplicationRuntimeException(ex);
            }
        }
        
        {
            setFeatureName("EXECUTAR_COMANDOS");
            setTitle("Executar comandos");
            setShortCutKey("F3");
            setPrimary(false);
        }
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="initFeatures">
    
    @Override
    protected void initFeatures() {
        addFeature(new FHome());
        addFeature(new FCustom());
        addFeature(new FDml());
        addFeature(new FExecutarComandos());
        super.initFeatures();
    }
    
    @Override
    public String getDefaultFeature() {
        return "HOME";
    }
    // </editor-fold>
    
    // <editor-fold defaultstate="collapsed" desc="Getters and Setters">
    
    public String getCommand() {
        return aCommand.getValue();
    }
    
    public void setCommand(String value) {
        this.aCommand.setValue(value);
    }
    
    public Integer getRegistros() {
        return aRegistros.getValue();
    }
    
    public void setRegistros(Integer value) {
        this.aRegistros.setValue(value);
    }
    
    public String getReplicar() {
        return aReplicar.getValue();
    }
    
    public void setReplicar(String value) {
        this.aReplicar.setValue(value);
    }
    // </editor-fold>
    
    /**
     * Declarações.
     * <p>
     * Objetos de instância.
     * <P>
     */
    // <user-code id="670453" parentId="670451" hashcode="3d8a7d0f" length="13414">
    // import br.com.telescope.t2.config.Config;
    // import br.com.telescope.t2.config.ConfigParameter;
    // import br.com.telescope.t2.ServiceUrlManager;
    // import br.com.telescope.t2.exception.UnexpectedException;
    // import br.com.telescope.t2.feature.AbstractFeature;
    // import br.com.telescope.util.HtmlUtil;
    // import java.util.Collection;
    
    private HashMap<String, Double> totalSumColumns = new HashMap<String, Double>();
    private ServiceModel service;
    private HashMap<String, Object> variables = new HashMap<String, Object>();
    private boolean notFound = false;
    
    private String expandMacros(String command) {
        for (String key : variables.keySet()) {
            Object obj = variables.get(key);
            if (obj == null) {
                obj = "";
            }
            command = command.replace("${" + key + "}", obj.toString());
        }
        return command;
    }
    
    /**
     * Exporta o resultado de um comando SQL para um arquivo CSV.
     * @param command COmando completo com a query a ser exportada
     * @throws AdsException 
     */
    private void executeExport(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1);
        }
        command = "select " + command.substring("export".length());
        CsvExportSource csv = new CsvExportSource();
        WebFileTemp tempFile = WebFileTemp.createInstance("csv");
        csv.setWriter(tempFile.getWriter());
        int count = 0;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(command);
            rs = ps.executeQuery();
            ResultSetMetaData meta = rs.getMetaData();
            Form form = new Form();
            ArrayList<String> columnNames = new ArrayList<String>();
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                String columnName = meta.getColumnLabel(i);
                columnNames.add(columnName);
                form.add(new DomText().name(columnName));
            }
            csv.setColumns(columnNames);
            csv.proccessHeader(form);
            while (rs.next()) {
                form.readFrom(rs);
                csv.proccess(form);
                count++;
            }
            ctx.p("<div>Arquivo gerado com " + count + " registros: <a href=\"" + tempFile.getUrl() + "\">Baixar</a>.</div>");
            notFound = (count == 0);
        } catch (SQLException ex) {
            ctx.p("<div class=\"alert error\"><h4>Ocorreu um erro ao executar a query!</h4>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
            csv.close();
        }
    }
    
    private void executeLet(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1);
        }
        int p = command.indexOf(" ");
        if (p >= 0) {
            String[] parms = command.substring(p).trim().toLowerCase().split("=");
            if (parms.length == 2) {
                String dest = parms[0].trim() + ".";
                String orig = parms[1].trim() + ".";
                int l = orig.length();
                HashMap<String,Object> add = new HashMap<String,Object>();
                for (String key : variables.keySet()) {
                    if (key.startsWith(orig)) {
                        Object value = variables.get(key);
                        add.put(dest + key.substring(l), value);
                    }
                }
                variables.putAll(add);
            }
        }
    }
    
    private void executeService(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        String[] arr = command.split(" ");
        if (arr.length > 1) {
            String sname = arr[1].trim().toUpperCase();
            try {
                Class<ServiceModel> clazz = ServiceUrlManager.getInstance().getClassForService(sname);
                service = clazz.newInstance();
                service.instantiate(ctx);
            } catch (Exception ex) {
                throw new UnexpectedException(ex);
            }
        }
    }
    
    private void executeFeature(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        String[] arr = command.split(" ");
        if (arr.length < 2) {
            service.run();
        } else {
            String name = arr[1].trim().toUpperCase();
            AbstractFeature feature = service.getFeature(name);
            if (feature == null) {
                throw new UnexpectedException("Service não possui funcionalidade " + name);
            }
            if (arr.length > 2) {
                feature.setAction(arr[2].trim().toUpperCase());
            }
            feature.run();
        }
    }
    
    private void executeConfig(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        Config cfg = Config.getInstance();
        int p = command.indexOf(" ");
        if (p >= 0) {
            String[] parms = command.substring(p).trim().split(",");
            for (String line : parms) {
                line = line.trim();
                p = line.indexOf("=");
                if (p < 0) {
                    if (line.endsWith(".*")) {
                        String key = line.substring(0, line.length() - 2);
                        cfg = Config.getInstance().getConfig(key);
                    }
                } else {
                    String key = line.substring(0, p).trim().toUpperCase();
                    String value = line.substring(p + 1).trim();
                    cfg.setParameter(key, value);
                }
            }
        }
    }
    
    private void executeParms(String command) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        int p = command.indexOf(" ");
        if (p >= 0) {
            String[] parms = command.substring(p).trim().split(",");
            for (String line : parms) {
                p = line.indexOf("=");
                String key = line.substring(0, p).trim().toUpperCase();
                String value = line.substring(p + 1).trim();
                ctx.setParameter(key, value);
            }
        }
    }
    
    private void executeShow(String command) {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        Config cfg = Config.getInstance();
        int p = command.indexOf(" ");
        if (p < 0) {
            for (String key : cfg.getParametersMap().keySet()) {
                String value = cfg.getParameter(key);
                ctx.p("<div><b>" + key 
                        + "</b> = <code>" 
                        + HtmlUtil.toHtmlText(value) + "</code></div>");
            }
        } else {
            String[] parms = command.substring(p).trim().split(",");
            for (String key : parms) {
                key = key.trim().toUpperCase();
                Collection<ConfigParameter> values = cfg.getParameters(key);
                for (ConfigParameter value :values) {
                    ctx.p("<div><b>" + value.getKey() 
                            + "</b> = <code>" 
                            + HtmlUtil.toHtmlText(value.getValue() )
                            + "</code></div>");
                }
            }
        }
        
    }
    private void executeConnect(String command) {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1).trim();
        }
        int p = command.indexOf(" ");
        if (p >= 0) {
            connectionName = command.substring(p).trim().toUpperCase();
        } else {
            connectionName = "DEFAULT";
        }
    }
    
    private String connectionName = "DEFAULT";
    
    private void executeDisplay(String command) throws AdsException {
        int maxlines = aRegistros.getValue();
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1);
        }
        command = "select " + command.substring("display".length());
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(command);
            Long init = System.currentTimeMillis();
            rs = ps.executeQuery();
            Long result = System.currentTimeMillis() - init;
            ResultSetMetaData meta = rs.getMetaData();
            ctx.p("<div>");
            ctx.p(result.toString());
            ctx.p("ms</div>");
            ctx.p("<hr>");
            int count = 0;
            while (rs.next() && maxlines-- > 0) {
                ctx.p("<table cellpadding=\"0\" cellspacing=\"0\" >");
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    String label = meta.getColumnLabel(i);
                    if (label.equals("?column?")) {
                        label = "";
                    } else {
                        label = "<b>" + label + "</b>: ";
                    }
                    ctx.p("<tr><td>" + label + "</td>");
                    Object obj = rs.getObject(i);
                    ctx.p("<td>" + obj + "</td>");
                    variables.put("last." + meta.getColumnLabel(i).toLowerCase(), obj);
                }
                ctx.p("</table><hr>");
                count++;
            }
            notFound = (count == 0);
        } catch (SQLException ex) {
            ctx.p("<div class=\"alert error\"><h4>Ocorreu um erro ao executar a query!</h4>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
        }
    }
    
    private void executeIfNotFoundCommand(String command) throws AdsException {
        if (notFound) {
            execute(command.substring(12).trim());
        }
    }
    
    private final static int INSERT = 1;
    private final static int UPDATE = 2;
    private final static int SAVE = 3;
    /**
     * 
     * @param command
     * @param mode: 1=INSERT, 2=UPDATE, 3=SAVE
     * @throws AdsException 
     */
    private void executeDmlCommand(String command, int mode) throws AdsException {
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1);
        }
        int p1 = command.indexOf(" ");
        command = command.substring(p1).trim();
        p1 = command.indexOf(" ");
        String table;
        if (p1 >= 0) {
            table = command.substring(0, p1).trim();
            command = command.substring(p1).trim();
        } else {
            table = command;
            command = "";
        }
        if (!command.toLowerCase().startsWith("select ")) {
            command = "select * from " + table + " " + command;
        }
        
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(command);
            Long init = System.currentTimeMillis();
            rs = ps.executeQuery();
            Long result = System.currentTimeMillis() - init;
            ResultSetMetaData meta = rs.getMetaData();
            ctx.p("<div>");
            ctx.p(result.toString());
            ctx.p("ms</div>");
            int count = 0;
            while (rs.next()) {
                String id = null;
                StringBuilder cols = new StringBuilder();
                StringBuilder vals = new StringBuilder();
                StringBuilder upds = new StringBuilder();
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    String col = meta.getColumnLabel(i);
                    Object obj = rs.getObject(i);
                    String val;
                    if (obj == null) {
                        val = "null";
                    } else if (obj instanceof Long 
                            || obj instanceof Integer
                            || obj instanceof Double
                            || obj instanceof Float) {
                        val = obj.toString();
                    } else {
                        val = "'" 
                                + obj.toString()
                                .replace("'", "''")
                                .replace("\n", "'||chr(10)||'")
                                .replace("\r", "'||chr(13)||'")
                                + "'";
                    }
                    if (col.equals("id")) {
                        id = val;
                    } else {
                        if (upds.length() > 0) upds.append(", ");
                        upds.append(col).append("=").append(val);
                    }
                    if (cols.length() > 0) cols.append(", ");
                    cols.append(col);
                    if (vals.length() > 0) vals.append(", ");
                    vals.append(val);
                    count++;
                }
                ctx.p("<p><code>");
                String dml;
                if (mode == UPDATE) {
                    dml = "update " + table + " set " + upds + " where id = " + id + ";";
                } else if (mode == INSERT) {
                    dml = "insert into " + table + " (" + cols + ") values (" + vals + ");";
                } else if (mode == SAVE) {
                    dml = "update " + table + " set " + upds + " where id = " + id + ";\n" +
                          "if-not-found insert into " + table + " (" + cols + ") values (" + vals + ");";
                } else {
                    throw new UnexpectedException("Chamada ilegal do método interno executeDmlCommand()");
                }
                ctx.p(HtmlUtil.toHtmlText(dml));
                ctx.p("</code></p>");
                count++;
            }
            notFound = (count == 0);
        } catch (SQLException ex) {
            ctx.p("<div class=\"alert error\"><h4>Ocorreu um erro ao executar a query!</h4>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
        }
    }
    // </user-code>
    
    /**
     * Executar comandos.
     * <p>
     * Executa os comandos informados no campo de comando.
     * <P>
     */
    public void executarComandos() throws AdsException {
        // <user-code id="806840" parentId="505672" hashcode="-6b764942" length="1202">
        // import br.com.telescope.t2.ServiceModel;
        // import br.com.telescope.t2.ServiceUrlManager;
        // import br.com.telescope.t2.config.Config;
        // import br.com.telescope.t2.feature.AbstractFeature;
        // import br.com.telescope.t2.exception.ApplicationRuntimeException;
        // import br.com.telescope.t2.exception.UnexpectedException;
        
        ctx.readFields(aCommand, aReplicar, aRegistros);
        
        doCustom();
        try {
            ctx.getConnection(connectionName).setAutoCommit(false);
        } catch (SQLException ex) {
            throw new AdsException(ex);
        }
        String text = aCommand.getValue().trim();
        if (!text.endsWith(";")) {
            text = text + ";";
        }
        String[] cmds = text.split("\n");
        StringBuilder cmd = new StringBuilder();
        for (String line : cmds) {
            cmd.append(" ").append(line);
            if (line.trim().endsWith(";")) {
                ctx.p("<code class=\"readonly-source-code\" data-language=\"text/x-plsql\">" + cmd.toString() + "</code>");
                execute(cmd.toString().trim());
                cmd = new StringBuilder();
            }
        }
        try {
            if (!ctx.getConnection(connectionName).getAutoCommit()) {
                ctx.getConnection(connectionName).commit();
            }
        } catch (SQLException ex) {
            throw new AdsException(ex);
        }
        // </user-code>
    }
    
    /**
     * Execute.
     * <p>
     * Executa um comando específico.
     * <P>
     */
    private void execute(String command) throws AdsException {
        // <user-code id="806841" parentId="505686" hashcode="355b3e06" length="2220">
        // import br.com.telescope.util.CsvExportSource;
        // import br.com.telescope.util.WebFileTemp;
        // import br.com.telescope.adsrt.dom.DomText;
        command = expandMacros(command);
        if (command.toUpperCase().startsWith("SELECT ")) {
            executeSelect(command);
        } else if (command.toUpperCase().startsWith("EXPORT ")) {
            executeExport(command);
        } else if (command.toUpperCase().startsWith("DISPLAY ")) {
            executeDisplay(command);
        } else if (command.toUpperCase().startsWith("IF-NOT-FOUND ")) {
            executeIfNotFoundCommand(command);
        } else if (command.toUpperCase().startsWith("DML-INSERT ")) {
            executeDmlCommand(command, INSERT);
        } else if (command.toUpperCase().startsWith("DML-UPDATE ")) {
            executeDmlCommand(command, UPDATE);
        } else if (command.toUpperCase().startsWith("DML-SAVE ")) {
            executeDmlCommand(command, SAVE);
        } else if (command.toUpperCase().startsWith("CONNECT ")) {
            executeConnect(command);
        } else if (command.toUpperCase().startsWith("DESC ")) {
            executeDesc(command);
        } else if (command.toUpperCase().startsWith("SUM ")) {
            executeSum(command);
        } else if (command.toUpperCase().startsWith("SHOW")) {
            executeShow(command);
        } else if (command.toUpperCase().startsWith("LET ")) {
            executeLet(command);
        } else if (command.toUpperCase().startsWith("CONFIG ")) {
            executeConfig(command);
        } else if (command.toUpperCase().startsWith("SERVICE ")) {
            executeService(command);
        } else if (command.toUpperCase().startsWith("RUN ")) {
            executeFeature(command);
        } else if (command.toUpperCase().startsWith("PARMS ")) {
            executeParms(command);
        } else if (command.equalsIgnoreCase("COMMIT")) {
            try {
                ctx.getConnection(connectionName).commit();
            } catch (SQLException ex) {
                throw new AdsException(ex);
            }
        } else if (command.equalsIgnoreCase("ROLLBACK")) {
            try {
                ctx.getConnection(connectionName).rollback();
            } catch (SQLException ex) {
                throw new AdsException(ex);
            }
        } else {
            if (ctx.userHasAccessTo(globalName() + ".DML")) {
                executeDml(command);
            } else {
                ctx.pushError("", AdsException.ERROR, "Usuário não autorizado a executar DML ou DDL!");
            }
        }
        // </user-code>
    }
    
    /**
     * Execute desc.
     * <p>
     * Apresenta uma descrição de uma tabela.
     * <P>
     */
    private void executeDesc(String command) throws AdsException {
        // <user-code id="806842" parentId="505704" hashcode="6c58f77f" length="1491">
        String entity = command.substring(5).trim().replaceAll(";", "");
        String sql = ctx.getSqlDialect().getPagedSelect(
                "select * from " + entity,
                1, null);
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(sql);
            Long init = System.currentTimeMillis();
            rs = ps.executeQuery();
            Long result = System.currentTimeMillis() - init;
            ResultSetMetaData meta = rs.getMetaData();
            ctx.p("<div>");
            ctx.p(result.toString());
            ctx.p("ms</div>");
            ctx.p("<div class=\"table-container\">");
            ctx.p("<table cellpadding=\"0\" cellspacing=\"0\" class=\"list\">");
            ctx.p("<tr>");
            ctx.p("<th>Column</th>");
            ctx.p("<th>Data type</th>");
            ctx.p("<th>Precision</th>");
            ctx.p("<th>Scale</th>");
            ctx.p("<th>Required</th>");
            ctx.p("</tr>");
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                ctx.p("<tr>");
                ctx.p("<td>" + meta.getColumnName(i) + "</td>");
                ctx.p("<td>" + meta.getColumnTypeName(i) + "</td>");
                ctx.p("<td>" + meta.getPrecision(i) + "</td>");
                ctx.p("<td>" + meta.getScale(i) + "</td>");
                ctx.p("<td align=center>" + ( meta.isNullable(i) == 1 ? " " : "*" ) + "</td>");
                ctx.p("</tr>");
            }
            ctx.p("</table>");
            ctx.p("</div>");
        } catch (SQLException ex) {
            ctx.p("<div style='color:red;'>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
        }
        // </user-code>
    }
    
    /**
     * Execute DML.
     * <p>
     * Executa um comando DML
     */
    private void executeDml(String command) throws AdsException {
        // <user-code id="806843" parentId="505698" hashcode="1dafe51c" length="534">
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(command);
            int count = ps.executeUpdate();
            notFound = count == 0;
            if (aReplicar.getValue().equals("S")) {
                Entity.logDmlRecord(ctx, command);
            }
            ctx.p("<br />" + count + " records processed!");
        } catch (SQLException ex) {
            ctx.p("<div class=\"alert error\"><h4>Ocorreu um erro ao executar a query!</h4>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
        }
        // </user-code>
    }
    
    /**
     * Execute Select.
     * <p>
     * Executa um comando do tipo SELECT
     * <P>
     * Apresenta uma tabela com os dados resultantes.
     * <P>
     */
    private void executeSelect(String command) throws AdsException {
        // <user-code id="806844" parentId="505692" hashcode="-3305f92" length="2465">
        // import br.com.telescope.util.JdbcUtil;
        // import java.util.HashMap;
        int maxlines = aRegistros.getValue();
        if (command.endsWith(";")) {
            command = command.substring(0, command.length() - 1);
        }
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = ctx.getConnection(connectionName).prepareStatement(command);
            Long init = System.currentTimeMillis();
            rs = ps.executeQuery();
            Long result = System.currentTimeMillis() - init;
            ResultSetMetaData meta = rs.getMetaData();
            ctx.p("<div>");
            ctx.p(result.toString());
            ctx.p("ms</div>");
            ctx.p("<div class=\"table-container\">");
            ctx.p("<table cellpadding=\"0\" cellspacing=\"0\" class=\"list fixed-table-header\">");
            ctx.p("<thead><tr>");
            for (int i = 1; i <= meta.getColumnCount(); i++) {
                ctx.p("<th>" + meta.getColumnLabel(i) + "</th>");
            }
            ctx.p("</tr></thead><tbody>");
            int count = 0;
            while (rs.next() && maxlines-- > 0) {
                ctx.p("<tr>");
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    Object obj = rs.getObject(i);
                    ctx.p("<td>" + obj + "</td>");
                    variables.put("last." + meta.getColumnLabel(i).toLowerCase(), obj);
                }
                // Aplicar soma total
                if (totalSumColumns.size() > 0) {
                    for (int i = 1; i <= meta.getColumnCount(); i++) {
                        String name = meta.getColumnName(i);
                        Double sum = totalSumColumns.get(name);
                        if (sum != null) {
                            totalSumColumns.put(name, sum + rs.getDouble(i));
                        }
                    }
                }
                ctx.p("</tr>");
                count++;
            }
            notFound = (count == 0);
            if (totalSumColumns.size() > 0) {
                ctx.p("<tr><th colspan=\"" + meta.getColumnCount() + "\"><b>Totais gerais</b></th></tr><tr>");
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    String name = meta.getColumnName(i);
                    Double sum = totalSumColumns.get(name);
                    if (sum != null) {
                        ctx.p("<td>" +  sum + "</td>");
                    } else {
                        ctx.p("<td></td>");
                    }
                }
                ctx.p("</tr>");
            }
            ctx.p("</tbody></table>");
            ctx.p("</div>");
        } catch (SQLException ex) {
            ctx.p("<div class=\"alert error\"><h4>Ocorreu um erro ao executar a query!</h4>" + ex.getMessage() + "</div>");
        } finally {
            JdbcUtil.close(ps, rs);
        }
        // </user-code>
    }
    
    /**
     * Execute SUM.
     * <p>
     * Executa a interpretação do comando SUM.
     * <P>
     */
    private void executeSum(String command) throws AdsException {
        // <user-code id="806886" parentId="670445" hashcode="1800a54e" length="410">
        if (command.endsWith(";")) {
            command = command.substring(0, command.length()-1);
        }
        totalSumColumns = new HashMap<String, Double>();
        String[] parts = command.split(" ");
        String mode = "sum";
        for (String part : parts) {
            if (mode.equals("sum")) {
                // Salta a palavra sum
                mode = "cols";
            } else if (mode.equals("cols")) {
                totalSumColumns.put(part.trim(), 0d);
            }
        }
        // </user-code>
    }
    
    /**
     * Inicializa replicação.
     */
    private void inicializaReplicacao() throws AdsException {
        // <user-code id="806887" parentId="505743" hashcode="-38ac51ef" length="107">
        aReplicar.setValue(Config.getInstance().getParameter("LOG_DML"));
        aRegistros.setValue("100");
        doCustom();
        // </user-code>
    }
    
    /**
     * Setar formato.
     * <p>
     * Seta o formato do editor para SQL
     */
    private void setarFormato() throws AdsException {
        // <user-code id="847998" parentId="847996" hashcode="7a0fcd59" length="35">
        aCommand.setFormat("language:sql");
        // </user-code>
    }
}
